/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "jm.h"

#define QP_LOCK_FILE  "#quickpool.lock"

static pid_t sintLockPID;
static char sstrLockFile[PATH_MAX+1];
static time_t stimeLastModified;
static unsigned int sintTried;

/**
 * @brief       Get access to lock file to start the master.
 *
 * @retval      TRUE         #1: ready to start the master
 * @retval      FALSE        #2: do not start the master as I'm cannot own the lockfile
 *
 * @note        To start the master daemon, I should get access to the lockfile.
 *              If there is no lock file, create one and write my name and pid to the lockfile.
 *              If there is a lock file, check if it was started by myself. If so, kill
 *              the old daemon and start a new one. If not, check the lock file modified time
 *              for three times before I become the new master.
 */
bool_t
get_lock(void)
{
    snprintf(sstrLockFile, PATH_MAX, "%s/%s",
             baseParams[BASE_WORKDIR].string_value, QP_LOCK_FILE);

    int force = 0;
    int fdLockfile = -1;

access:
    if (force) {
        fdLockfile = open(sstrLockFile, O_RDWR | O_CREAT | O_TRUNC, 0644);
    } else {
        fdLockfile = open(sstrLockFile, O_RDWR | O_CREAT | O_EXCL, 0644);
    }

    char buf[MAXHOSTNAMELEN+32+1];
    if (fdLockfile >= 0) {

        snprintf(buf, MAXHOSTNAMELEN+32, "%s:%d", gptrMyHost->name, (int)getpid());

        unsigned int len;
        len = strlen(buf);

        if (len != write(fdLockfile, buf, len)) {
            log_message(ERR, NOCLASS, "%s: Failed to write lock file <%s>: %m",
                        __func__, sstrLockFile);
            jm_die(JM_QUIT_LOCKFILE);
	}

        close(fdLockfile);

        sintLockPID = getpid();

        log_message(INFO, NOCLASS, "%s: Got lock file", __func__);

        return TRUE;
    }

    if (errno != EEXIST) {
        log_message(ERR, NOCLASS, "%s: Failed in opening lock file <%s>: %m",
                    __func__, sstrLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    fdLockfile = open(sstrLockFile, O_RDONLY, 0444);
    if (fdLockfile < 0) {
        log_message(ERR, NOCLASS, "%s: cannot open existing lock file <%s>: %m",
                    __func__, sstrLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    int i=0, cc;
    i = 0;
    while ((cc = read(fdLockfile, &buf[i], 1)) == 1) {

        if (buf[i] != ':') {
            i++;
            continue;
        }

        buf[i] = '\0';
        if (!issame_host(gptrMyHost->name, buf)) {
            break;
        }

        i = 0;
        while ((cc=read(fdLockfile, &buf[i], 1)) == 1)
            i++;

        buf[i] = '\0';

        int pid;
        pid = atoi(buf);
        if (kill(pid, 0) < 0) {
            log_message(ERR, NOCLASS, "%s: Last owner of lock file was on this host with pid <%d>; attempting to take over lock file",
                        __func__, pid);
            close(fdLockfile);
            force = 1;
            goto access;
        }

        break;
    }

    close(fdLockfile);

    struct stat statbuf;
    if (stat(sstrLockFile, &statbuf) < 0) {

        if (errno == ENOENT)
            goto access;

        log_message(ERR, NOCLASS, "%s: stat() failed on %s: %m", __func__, sstrLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    if (statbuf.st_mtime == stimeLastModified) {
        if (sintTried > 3) {

            log_message(ERR, NOCLASS, "%s: Previous jm appears dead; attempting to take over lock file", __func__);
            force = 1;

            goto access;
        } else {
            sintTried++;
        }
    } else {

        stimeLastModified = statbuf.st_mtime;
        sintTried = 0;
    }

    return FALSE;
} // end function get_lock

/**
 * @brief       Release lock before I'm die.
 *
 * @param[in]   sig          #1: signal jm cached
 *
 * @note        Unlink the lock file before I'm quit.
 */
void
release_lock(int sig)
{
    if (sintLockPID != getpid()) {
        return;
    }

    //if jm die because of system signal,don't write the logfile    
    if (unlink(sstrLockFile) < 0) {
        if (100 < sig) {
            log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "unlink", sstrLockFile);
        }
    } else {
        if (100 < sig) {    
            log_message(INFO, NOCLASS, "%s: Released lock file", __func__);
        }
    }

    return;
} // end function get_lock

/**
 * @brief       Update the lock file from time to time.
 *
 * @note        When I'm alive, keep updating the lock file to avoid others starting
 *              a new master.
 */
void
update_lock(void)
{
    int cc = 0;
    int fdLockFile;
    do {
        fdLockFile = open(sstrLockFile, O_RDWR, 0644);
    } while ((fdLockFile < 0) && (errno == EINTR) && (cc++ < 10));

    if (fdLockFile < 0) {

        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "open", sstrLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    if (lseek(fdLockFile, 0, SEEK_SET) != 0) {

        log_message(ERR, NOCLASS, STR_FUNC_S_D_FAIL_M, __func__, "lseek", sstrLockFile, fdLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    char buf[2];
    if ((cc = read(fdLockFile, buf, 1)) != 1) {
        if (cc < 0) {

	    log_message(ERR, NOCLASS, STR_FUNC_S_D_FAIL_M, __func__, "read", sstrLockFile, fdLockFile);
        } else {

	    log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "read", sstrLockFile);
        }

        jm_die(JM_QUIT_LOCKFILE);
    }

    if (lseek(fdLockFile, 0, SEEK_SET) != 0) {

        log_message(ERR, NOCLASS, STR_FUNC_S_D_FAIL_M, __func__, "lseek", sstrLockFile, fdLockFile);
        jm_die(JM_QUIT_LOCKFILE);
    }

    if ((cc = write(fdLockFile, buf, 1)) != 1) {
        if (cc < 0) {

	    log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "write", sstrLockFile);
        } else {

	    log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "write", sstrLockFile);
        }

        jm_die(JM_QUIT_LOCKFILE);
    }

    if (close(fdLockFile) != 0) {
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "close", sstrLockFile);
    }

    return;
} // end function update_lock
